﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Protocolo.Mensajes.Request;
using Protocolo.Mensajes.Response;
using System.IO;
using System.Threading;
using Protocolo.Utilidades;
using Protocolo;
using Protocolo.Enums;
using Protocolo.Mensajes;

namespace Servidor
{
    public class ProcesosAtencionClientes
    {
        // Permite acceso seguro de varios subprocesos
        private volatile bool _shouldStop;

        public void AtenderClientesTCP(object tcpListener)
        {
            while (!_shouldStop)
            {
                var comunicacionTCP = new ComunicacionTCP();
                comunicacionTCP.EscucharRequests((TcpListener)tcpListener);

                try
                {
                    //Recibo mensaje del cliente
                    var mensaje = comunicacionTCP.RecibirMensaje();

                    Utilities.LogMessage("Mensaje TCP recibido");

                    if (mensaje.Header == TipoMensajeEnum.REQ)
                    {
                        if (mensaje.Cmd == ComandoEnum.SubirArchivo)
                        {
                            SubirArchivo(comunicacionTCP, (SubirArchivoRequest)mensaje);
                        }
                        else if (mensaje.Cmd == ComandoEnum.DescargarArchivo)
                        {
                            DescargarArchivo(comunicacionTCP, (DescargarArchivoRequest)mensaje);
                        }
                        else
                        {
                            Utilities.LogError(string.Format("Mensaje recibido no es el esperado: {0} - {1}", mensaje.Header.ToString(), mensaje.Cmd.ToString()));
                        }
                    }
                    else
                    {
                        Utilities.LogError(string.Format("Mensaje recibido no es el esperado: {0} - {1}", mensaje.Header.ToString(), mensaje.Cmd.ToString()));
                    }
                }
                catch (Exception ex)
                {
                    Utilities.LogError(string.Format("Error recibiendo mensaje TCP: {0}", ex.Message));
                }
                finally
                {
                    comunicacionTCP.CerrarConexion();
                }
            }

            if (_shouldStop)
            {
                throw new ServerException("Fin Proceso", "SomeCustomMessage", true);
            }
        }

        private void SubirArchivo(ComunicacionTCP comunicacionTCP, SubirArchivoRequest request)
        {
            var response = new SubirArchivoResponse();
            Utilities.LogMessage(string.Format("Procesando pedido de subida de archivo {0}", request.NombreArchivo));

            try
            {
                //Valido si el archivo no existe y envio ok para comenzar a recibir el archivo
                Utilities.LogMessage("Validando que el archivo no exista en el servidor");
                response.Resultado = DatosServidor.Instancia.ExisteArchivo(request.NombreArchivo) ? ResultadoTransferenciaArchivoEnum.ArchivoYaExiste : ResultadoTransferenciaArchivoEnum.OK;

                //Envio respuesta
                comunicacionTCP.EnviarMensaje(response);

                if (response.Resultado == ResultadoTransferenciaArchivoEnum.OK)
                {
                    Utilities.LogMessage("Archivo no existe en el servidor, comienza subida");

                    //Recibo el archivo
                    string checksum = comunicacionTCP.RecibirArchivo(string.Format("{0}\\{1}", DatosServidor.Instancia.FileRepository, request.NombreArchivo), true, request.LargoArchivo, DatosServidor.Instancia.AnchoDeBandaServidor);

                    //Valido si el archivo se almaceno correctamente
                    Utilities.LogMessage("Archivo recibido, validando checksum");
                    response = new SubirArchivoResponse();
                    response.Resultado = (request.MD5 == checksum) ? ResultadoTransferenciaArchivoEnum.OK : ResultadoTransferenciaArchivoEnum.ErrorChecksum;

                    //Guardo el archivo en la lista o lo borro si es incorrecto
                    if (response.Resultado == ResultadoTransferenciaArchivoEnum.OK)
                    {
                        DatosServidor.Instancia.AgregarArchivo(request.NombreArchivo, request.MD5);
                        Utilities.LogExito("Archivo almacenado correctamente en el servidor");
                    }
                    else
                    {
                        Archivos.EliminarArchivo(string.Format("{0}\\{1}", DatosServidor.Instancia.FileRepository, request.NombreArchivo));
                        Utilities.LogError(string.Format("Error recibiendo archivo: {0}", ObtenerMensajeEnum.ResultadoTransferenciaArchivo(response.Resultado)));
                    }

                    comunicacionTCP.EnviarMensaje(response);
                }
                else
                {
                    Utilities.LogError(string.Format("Error subiendo archivo: {0}", ObtenerMensajeEnum.ResultadoTransferenciaArchivo(response.Resultado)));
                }
            }
            catch (Exception ex)
            {
                Archivos.EliminarArchivo(string.Format("{0}\\{1}", DatosServidor.Instancia.FileRepository, request.NombreArchivo));
                response.Resultado = ResultadoTransferenciaArchivoEnum.ErrorDeConexion;
                Utilities.LogError(string.Format("Error recibiendo archivo: {0}", ex.Message));
                comunicacionTCP.EnviarMensaje(response);
            }
        }

        private void DescargarArchivo(ComunicacionTCP comunicacionTCP, DescargarArchivoRequest request)
        {
            var response = new DescargarArchivoResponse();
            Utilities.LogMessage(string.Format("Procesando pedido de descarga de archivo {0}", request.NombreArchivo));

            try
            {
                byte[] archivo = null;

                //Valido si el archivo existe y envio ok para comenzar a enviar el archivo
                Utilities.LogMessage("Validando que el archivo exista en el servidor");
                response.Resultado = DatosServidor.Instancia.ExisteArchivo(request.NombreArchivo) ? ResultadoTransferenciaArchivoEnum.OK : ResultadoTransferenciaArchivoEnum.ArchivoNoExiste;
                response.MD5 = (response.Resultado == ResultadoTransferenciaArchivoEnum.OK) ? DatosServidor.Instancia.ObtenerMD5(request.NombreArchivo) : string.Empty;
                
                //Leo el archivo del repositorio
                if (response.Resultado == ResultadoTransferenciaArchivoEnum.OK)
                {
                    archivo = Archivos.LeerArchivo(string.Format("{0}\\{1}", DatosServidor.Instancia.FileRepository, request.NombreArchivo));
                    response.LargoArchivo = archivo.LongLength;
                }

                //Envio respuesta
                comunicacionTCP.EnviarMensaje(response);

                if (response.Resultado == ResultadoTransferenciaArchivoEnum.OK)
                {
                    Utilities.LogMessage("Archivo existe en el servidor, esperando confirmacion para comenzar a transferir");
                    //Espero request para comenzar a enviar archivo (este mensaje es para sincronizar con el cliente solamente)
                    request = (DescargarArchivoRequest)comunicacionTCP.RecibirMensaje();

                    Utilities.LogMessage("Envio confirmado, comienza a transferir archivo");

                    //Obtengo los bytes a enviar a partir de lo que me pidieron
                    byte[] archivoAEnviar = new byte[archivo.LongLength - request.ByteDesde];
                    Array.Copy(archivo, request.ByteDesde, archivoAEnviar, 0, archivoAEnviar.LongLength);

                    //Envio el archivo
                    comunicacionTCP.EnviarArchivo(archivoAEnviar, DatosServidor.Instancia.AnchoDeBandaServidor);

                    Utilities.LogExito("Archivo enviado con exito");
                }
                else
                {
                    Utilities.LogError(string.Format("Error enviando archivo: {0}", ObtenerMensajeEnum.ResultadoTransferenciaArchivo(response.Resultado)));
                }
            }
            catch (Exception ex)
            {
                response.Resultado = ResultadoTransferenciaArchivoEnum.ErrorDeConexion;
                Utilities.LogError(string.Format("Error recibiendo archivo: {0}", ex.Message));
                comunicacionTCP.EnviarMensaje(response);
            }
        }

        public void AtenderDescubrimientoClientes()
        {
            while (!_shouldStop)
            {
                try
                {
                    //Recibir mensaje cliente
                    var request = new DescubrirServidorRequest();
                    request.RecibirBroadcast(DatosServidor.Instancia.PuertoUDPClientes);
                    
                    Utilities.LogMessage(string.Format("Mensaje descubrimiento recibido desde: {0}", request.IPCliente));

                    //Valido si soy el servidor con mas ancho de banda disponible
                    if (AtenderCliente())
                    {
                        //Enviar Respuesta a cliente
                        var response = new DescubrirServidorResponse();
                        response.IPServidor = DatosServidor.Instancia.IPServidor;
                        response.PuertoServidor = DatosServidor.Instancia.PuertoTCPClientes;
                        response.EnviarUDP(request.IPCliente, DatosServidor.Instancia.PuertoUDPClientes);

                        Utilities.LogExito(string.Format("Respuesta enviada a: {0}", request.IPCliente));
                    }
                    else
                    {
                        Utilities.LogMessage(string.Format("No envio respuesta a pedido de {0} ya que hay servidores con mejor ancho de banda", request.IPCliente));
                    }
                }
                catch (Exception ex)
                {
                    Utilities.LogError(string.Format("Error atendiendo descubrimiento clientes: {0}", ex.Message));
                }
            }
        }

        private static bool AtenderCliente()
        {
            //Obtengo el ancho de banda de los servidores activos
            //Para ser un servidor activo su ancho de banda tiene que estar actualizado
            var servidoresActivos = (from datosServ in DatosServidor.Instancia.Servidores_AnchoDeBanda.Values
                                     where datosServ.FechaRecibido.AddSeconds(datosServ.IntervaloSincronizacion) >= DateTime.Now
                                     select datosServ);

            //Obtengo el mayor ancho de banda de los servidores activos
            double maxAnchoDeBanda = servidoresActivos.Count() == 0 ? 0 : servidoresActivos.Max(s => s.AnchoDeBanda);

            return DatosServidor.Instancia.AnchoDeBandaServidor.AnchoDeBandaDisponible >= maxAnchoDeBanda;
        }

        public void RequestStop()
        {
            _shouldStop = true;
        }

    }
}
